import {
  Component,
  OnInit,
  Output,
  EventEmitter,
  ElementRef,
} from "@angular/core";
import { PL00Lexer } from "../../antlr/PL00Lexer";
import { PL00Parser } from "../../antlr/PL00Parser";
import { PL00Error } from "./PL00Error";
import { PL00InterpretVistorFactory } from "../../interpreter/PL00InterpretVisitor";
import {
  CommonTokenStream,
  error,
  InputStream,
  Parser,
} from "../../../node_modules/antlr4/index.js";
import { DefaultErrorStrategy } from "../../../node_modules/antlr4/error/ErrorStrategy.js";
import { PL00QuickVisitorFactory } from "src/interpreter/PL00QuickVisitor";
import { PL00SymbolTable } from "src/interpreter/PL00SymbolTable";
import { BaseSymbol, FunctionSymbol } from "src/interpreter/PL00Symbols";
const keywords = [
  "break",
  "continue",
  "const",
  "var",
  "procedure",
  "function",
  "call",
  "return",
  "begin",
  "end",
  "if",
  "then",
  "else",
  "while",
  "do",
];
import { NzMessageService } from "ng-zorro-antd/message";

@Component({
  selector: "app-pl00-editor",
  templateUrl: "./pl00-editor.component.html",
  styleUrls: ["./pl00-editor.component.css"],
})
export class Pl00EditorComponent implements OnInit {
  // monaco editor的主题和语言配置
  editorOptions = { theme: "pl00-white", language: "pl00" };
  // monaco editor实例
  editor: monaco.editor.ICodeEditor;
  // monaco editor实例的模型
  model: monaco.editor.ITextModel;
  // 自动补全表
  symbolTable: PL00SymbolTable = null;
  // 自动输入
  inputText: string[] = [];
  // 自动输入指针
  inputCursor = 0;
  // 自动运行限制
  autoLoopRestriction = 500;
  // 手动运行限制
  maxLoopRestriction = 1000000;
  // 发送错误
  @Output() errorsEmitter: EventEmitter<PL00Error[]> = new EventEmitter<
    PL00Error[]
  >();
  // 发送访问器
  @Output() visitorEmitter: EventEmitter<object> = new EventEmitter<object>();
  // 发送输出
  @Output() outputEmitter: EventEmitter<string> = new EventEmitter<string>();
  // 记录加载过的模块
  modules: { [moduleName: string]: { [functionName: string]: any } } = {};
  // 错误记录
  errors: PL00Error[] = [];
  get code(): string {
    return this._code;
  }
  // 初始化代码
  _code: string = `import matrix;
var a,b:Matrix;
begin
    a:=createMatrix(2,2);
    a.set(0,0,1.0);
    a.set(1,1,1.0);
    b:=createMatrix(2,2);
    b.set(0,0,1.0);
    b.set(1,1,1.0);
    write(a+b)
end.`;
  set code(value: string) {
    this._code = value;
  }
  // 获得自动补全源
  getCompletionProvider(component: Pl00EditorComponent) {
    class IdentCompletionItemProvider
      implements monaco.languages.CompletionItemProvider {
      // 触发字符
      triggerCharacters = [".", "("];
      provideCompletionItems(
        model: monaco.editor.ITextModel,
        position: monaco.Position,
        context: monaco.languages.CompletionContext,
        token: monaco.CancellationToken
      ): monaco.languages.ProviderResult<monaco.languages.CompletionList> {
        const result: monaco.languages.CompletionList = {
          incomplete: true,
          suggestions: [],
        };
        const word = model.getWordUntilPosition(position);
        const range: monaco.IRange = {
          startLineNumber: position.lineNumber,
          endLineNumber: position.lineNumber,
          startColumn: word.startColumn,
          endColumn: word.endColumn,
        };
        if (component.symbolTable !== null) {
          for (const symbolName in component.symbolTable.table) {
            const symbols = component.symbolTable.table[symbolName];
            if (symbols.length === 0) {
              continue;
            }
            const symbol = symbols[symbols.length - 1];
            // 获取一个代表符号
            // 如果是函数/过程的提示
            if (["Function", "Procedure"].includes(symbol.type)) {
              result.suggestions.push({
                label: symbolName,
                insertText: symbolName,
                kind: monaco.languages.CompletionItemKind.Function,
                range,
                detail:
                  symbol.type === "Function" && symbols.length === 1
                    ? (symbol as FunctionSymbol).params
                        .map((x) => x.name + ":" + x.type)
                        .join(",")
                    : "",
              });
            } else {
              result.suggestions.push({
                label: symbolName,
                insertText: symbolName,
                kind: monaco.languages.CompletionItemKind.Variable,
                range,
                detail: symbol.type,
              });
            }
          }
        }
        // 关键词补全
        for (const keyword of keywords)
          result.suggestions.push({
            label: keyword,
            insertText: keyword,
            kind: monaco.languages.CompletionItemKind.Keyword,
            range,
          });
        return result;
      }
    }
    return IdentCompletionItemProvider;
  }
  onInit(editor) {
    this.editor = editor;
    this.model = this.editor.getModel();
    // 事件绑定
    this.model.onDidChangeContent((event) => {
      // console.log(event);
      this.validate();
      // console.log(this.symbolTable)
    });
    // 注册补全源
    monaco.languages.registerCompletionItemProvider(
      "pl00",
      new (this.getCompletionProvider(this))()
    );
  }
  // 试探运行
  validate() {
    this.errors = [];
    const chars = new InputStream(this.code);
    // 词法分析设置
    const lexer = new PL00Lexer(chars);
    // lexer.strictMode = false;
    lexer.removeErrorListeners();
    let errorListener = new ErrorCollectorListener(this.errors);
    lexer.addErrorListener(errorListener);
    if (this.errors.length === 0) {
      // 语法分析设置
      const tokens = new CommonTokenStream(lexer);
      const parser = new PL00Parser(tokens);
      parser.buildParserTrees = true;
      parser.removeErrorListeners();
      parser.addErrorListener(new ErrorCollectorListener(this.errors));
      parser._errHandler = new PL00ErrorStrategy();
      const tree = parser.program();
      if (this.errors.length === 0) {
        // 语义分析
        const quickVisitor = new (PL00QuickVisitorFactory(
          this.modules
        ) as any)();
        quickVisitor.visitProgram(tree);
        this.errors = quickVisitor.errors;
        this.symbolTable = quickVisitor.symbolTable;
        if (this.errors.length === 0) {
          // 试运行
          let output = "";
          const visitor = new (PL00InterpretVistorFactory(
            (x) => (output += x),
            () => this.requestInput(false),
            this.modules
          ) as any)();
          output = "";
          visitor.loopRestriction = this.autoLoopRestriction;
          try {
            visitor.visitProgram(tree);
          } catch (e) {
            if (
              e.message.startsWith("超出") &&
              this.autoLoopRestriction !== 0
            ) {
              this.message.info(e.message);
            } else {
              throw e;
            }
          }
          if (visitor.completed === true) {
            this.outputEmitter.emit(output);
          }
          this.visitorEmitter.emit(visitor);
          // this.message.success("自动运行完成", { nzDuration: 1000 });
        }
      }
    }
    this.resetInput();
    // console.log(this.errors);
    monaco.editor.setModelMarkers(this.model, "xyqlx", this.errors);
    this.errorsEmitter.emit(this.errors);
  }
  // 获取焦点并跳到某一行
  jumpToLine(error: PL00Error) {
    this.editor.focus();
    this.editor.revealLine(error.startLineNumber);
    this.editor.setPosition(
      new monaco.Position(error.startLineNumber, error.startColumn)
    );
  }
  // 重置自动输入缓冲区
  resetInput() {
    this.inputCursor = 0;
  }
  // 输入，分流到自动输入/输入框/停止
  requestInput(certain = false) {
    if (this.inputCursor === this.inputText.length) {
      if (certain) {
        const s = prompt("Please enter input:");
        return s;
      }
      return "0";
    }
    const result = this.inputText[this.inputCursor];
    this.inputCursor++;
    return result;
  }
  // 手动运行
  run(): void {
    this.message.info("启动运行");
    var chars = new InputStream(this.code);
    var lexer = new PL00Lexer(chars);
    var tokens = new CommonTokenStream(lexer);
    // tokens.fill()
    // console.log(tokens)
    var parser = new PL00Parser(tokens);
    parser.buildParserTrees = true;
    var tree = parser.program();
    let output = "";
    const visitor = new (PL00InterpretVistorFactory(
      (x) => (output += x),
      () => this.requestInput(true),
      this.modules
    ) as any)();
    visitor.loopRestriction = this.maxLoopRestriction;
    output = "";
    try {
      visitor.visitProgram(tree);
    } catch (e) {
      if (e.message.startsWith("超出")) {
        this.message.info(e.message);
      } else {
        throw e;
      }
    }
    // console.log(visitor);
    this.outputEmitter.emit(output);
    this.visitorEmitter.emit(visitor);
    this.resetInput();
  }
  constructor(
    private elementRef: ElementRef,
    private message: NzMessageService
  ) {}

  ngOnInit(): void {}
}
// 收集错误
class ErrorCollectorListener extends error.ErrorListener {
  private errors: PL00Error[] = [];
  constructor(errors: PL00Error[]) {
    super();
    this.errors = errors;
  }
  syntaxError(recognizer, offendingSymbol, line, column, msg, e) {
    let endColumn = column + 1;
    if (offendingSymbol._text !== null) {
      endColumn = column + offendingSymbol._text.length;
    }
    this.errors.push(new PL00Error(line, line, column, endColumn, msg));
    // console.log(new Error(line, line, column, endColumn, msg));
  }
}
// 恢复错误（没有什么特殊的设计，所以恢复策略并不是很好）
class PL00ErrorStrategy extends DefaultErrorStrategy {
  reportUnwantedToken(recognizer: Parser) {
    return super.reportUnwantedToken(recognizer);
  }
  recover(recognizer, e) {}

  singleTokenDeletion(recognizer: Parser) {
    var nextTokenType = recognizer.getTokenStream().LA(2);
    // const num = recognizer.getTokenStream().LA(1);
    // console.log(recognizer.symbolicNames[num]);
    var expecting = this.getExpectedTokens(recognizer);
    if (expecting.contains(nextTokenType)) {
      this.reportUnwantedToken(recognizer);
      recognizer.consume();
      var matchedSymbol = recognizer.getCurrentToken();
      this.reportMatch(recognizer);
      return matchedSymbol;
    } else {
      return null;
    }
  }
  getExpectedTokens = function (recognizer) {
    return recognizer.getExpectedTokens();
  };
  reportMatch = function (recognizer) {
    this.endErrorCondition(recognizer);
  };
}
